/**
 * Copyright (C) 2007 The AsyncMail Group <asyncmail@googlegroups.com>
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *         http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.asyncmail.smtpserver.mina;

import org.apache.commons.logging.Log;
import org.apache.mina.common.IdleStatus;
import org.apache.mina.common.IoHandlerAdapter;
import org.apache.mina.common.IoSession;
import org.springframework.beans.factory.annotation.Required;

import com.googlecode.asyncmail.common.ExtensibleHandler;
import com.googlecode.asyncmail.common.WiringException;
import com.googlecode.asyncmail.smtp.SMTPRequest;
import com.googlecode.asyncmail.smtp.SMTPResponse;
import com.googlecode.asyncmail.smtp.SMTPResponseImpl;
import com.googlecode.asyncmail.smtp.SMTPRetCode;
import com.googlecode.asyncmail.smtpserver.SMTPHandlerConfigurationData;
import com.googlecode.asyncmail.smtpserver.SMTPSession;
import com.googlecode.asyncmail.smtpserver.core.CommandHandler;
import com.googlecode.asyncmail.smtpserver.core.ConnectHandler;
import com.googlecode.asyncmail.smtpserver.core.UnknownCmdHandler;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

public class SMTPCommandDispatcherIoHandler extends IoHandlerAdapter implements
        ExtensibleHandler {

    private final static String SMTP_SESSION = "com.googlecode.asyncmail.smtpserver.SMTPCommandDispatcherIoHandler.SMTP_SESSION";

    /**
     * The list of available command handlers
     */
    private Map<String, List<CommandHandler>> commandHandlerMap = new HashMap<String, List<CommandHandler>>();

    private final CommandHandler unknownHandler = new UnknownCmdHandler();

    // private final static String[] mandatoryCommands = { "MAIL" , "RCPT",
    // "DATA"};
    private final static String[] mandatoryCommands = { "MAIL", "RCPT", "QUIT" };

    private Log logger;

    private List<ConnectHandler> connectHandlers;

    private SMTPHandlerConfigurationData smtpHandlerConf;

    @Required
    public void setLogger(Log logger) {
        this.logger = logger;
    }

    @Required
    public void setSMTPHandlerConfigurationData(
            SMTPHandlerConfigurationData smtpHandlerConf) {
        this.smtpHandlerConf = smtpHandlerConf;
    }

    public void onSMTPRequest(IoSession session, SMTPRequest request) {
        List<CommandHandler> commandHandlers = getCommandHandlers(request
                .getCommand());
        // fetch the command handlers registered to the command
        if (commandHandlers == null) {
            // end the session
            SMTPResponseImpl resp = new SMTPResponseImpl(SMTPRetCode.LOCAL_ERROR,
                    "Local configuration error: unable to find a command handler.");
            resp.setEndSession(true);
            session.write(resp);
        } else {
            int count = commandHandlers.size();
            for (int i = 0; i < count; i++) {
                SMTPResponse response = commandHandlers.get(i).onCommand(
                        (SMTPSession) session.getAttribute(SMTP_SESSION),
                        request);

                // if the response is received, stop processing of command
                // handlers
                if (response != null) {
                    session.write(response);
                    break;
                }

                // NOTE we should never hit this line, otherwise we ended the
                // CommandHandlers with
                // no responses.
                // (The note is valid for i == count-1)
            }

        }
    }

    /**
     * @see com.googlecode.asyncmail.common.ExtensibleHandler#getMarkerInterfaces()
     */
    public List<Class<?>> getMarkerInterfaces() {
        List<Class<?>> res = new LinkedList<Class<?>>();
        res.add(CommandHandler.class);
        res.add(ConnectHandler.class);
        return res;
    }

    public void setCommandHandlers(List<CommandHandler> commandHandlers)
            throws WiringException {
        this.commandHandlerMap = new HashMap<String, List<CommandHandler>>();

        for (Iterator<CommandHandler> it = commandHandlers.iterator(); it
                .hasNext();) {
            CommandHandler handler = it.next();
            Collection<String> implCmds = handler.getImplCommands();

            for (Iterator<String> i = implCmds.iterator(); i.hasNext();) {
                String commandName = i.next().trim().toUpperCase(Locale.US);
                if (logger.isInfoEnabled()) {
                    logger.info("Added Commandhandler: " + handler.getClass()
                            + " for command " + commandName);
                }
                addToMap(commandName, handler);
            }
        }

        addToMap(UnknownCmdHandler.UNKNOWN_COMMAND, unknownHandler);

        if (commandHandlerMap.size() < 2) {
            if (logger.isErrorEnabled()) {
                logger.error("No commandhandlers configured");
            }
            throw new WiringException("No commandhandlers configured");
        } else {
            boolean found = true;
            for (int i = 0; i < mandatoryCommands.length; i++) {
                if (!commandHandlerMap.containsKey(mandatoryCommands[i])) {
                    if (logger.isErrorEnabled()) {
                        logger
                                .error("No commandhandlers configured for the command:"
                                        + mandatoryCommands[i]);
                    }
                    found = false;
                    break;
                }
            }

            if (!found) {
                throw new WiringException(
                        "No commandhandlers configured for mandatory commands");
            }

        }
    }

    /**
     * @throws WiringException
     * @see com.googlecode.asyncmail.common.ExtensibleHandler#wireExtensions(java.lang.Class,
     *      java.util.List)
     */
    @SuppressWarnings("unchecked")
    public void wireExtensions(Class interfaceName, List extension)
            throws WiringException {
        if (CommandHandler.class.equals(interfaceName)) {
            setCommandHandlers(extension);

        } else if (ConnectHandler.class.equals(interfaceName)) {
            setConnectHandlers(extension);
        }

    }

    public void setConnectHandlers(List<ConnectHandler> extension) {
        this.connectHandlers = extension;
    }

    /**
     * Add it to map (key as command name, value is an array list of
     * commandhandlers)
     * 
     * @param commandName
     *                the command name which will be key
     * @param cmdHandler
     *                The commandhandler object
     */
    private void addToMap(String commandName, CommandHandler cmdHandler) {
        logger.debug("addToMap: " + commandName + " = " + cmdHandler);
        List<CommandHandler> handlers = commandHandlerMap.get(commandName);
        if (handlers == null) {
            handlers = new ArrayList<CommandHandler>();
            commandHandlerMap.put(commandName, handlers);
        }
        handlers.add(cmdHandler);
    }

    /**
     * Returns all the configured commandhandlers for the specified command
     * 
     * @param command
     *                the command name which will be key
     * @return List of commandhandlers
     */
    List<CommandHandler> getCommandHandlers(String command) {
        if (command == null) {
            return null;
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Lookup command handler for command: " + command);
        }
        List<CommandHandler> handlers = commandHandlerMap.get(command);
        if (handlers == null) {
            handlers = commandHandlerMap.get(UnknownCmdHandler.UNKNOWN_COMMAND);
        }

        return handlers;
    }

    @Override
    public void exceptionCaught(IoSession arg0, Throwable arg1)
            throws Exception {
        logger.error("Caught exception: " + arg1.getMessage());
        // just close session
        arg0.close();
    }

    @Override
    public void messageReceived(IoSession session, Object arg1)
            throws Exception {
        if (arg1 instanceof SMTPRequest) {
            onSMTPRequest(session, (SMTPRequest) arg1);
        } else {
            logger.error("Invalid message object");
        }
    }

    @Override
    public void sessionCreated(IoSession arg0) throws Exception {
        // Timeout 120 Seconds
        arg0.setIdleTime(IdleStatus.BOTH_IDLE, 120);

        // Add attributes
        arg0.setAttribute(SMTP_SESSION, new SMTPSessionImpl(smtpHandlerConf,
                arg0));

    }

    @Override
    public void sessionIdle(IoSession arg0, IdleStatus arg1) throws Exception {
        logger.debug("Connection timed out");
        arg0.write("Connection timeout");
    }

    @Override
    public void sessionOpened(IoSession arg0) throws Exception {
        // Call the connectHandlers
        if (connectHandlers != null) {
            for (int i = 0; i < connectHandlers.size(); i++) {
                connectHandlers.get(i).onConnect(
                        (SMTPSession) arg0.getAttribute(SMTP_SESSION));
            }
        }
    }

}
